/*
 * @Description: 检查版本更新
 * @Author: xinqiao lxq1990.0716@qq.com
 * @Date: 2023-04-06
 * @LastEditors: xinqiao lxq1990.0716@qq.com
 * @LastEditTime: 2023-04-11
 */
interface Options {
  /** 轮询时间，单位毫秒 */
  timer?: number
}

/**
 * 检查版本更新
 */
class Updater {
  oldScript: Set<string> //存储第一次值也就是script 的hash 信息
  newScript: Set<string> //获取新的值 也就是新的script 的hash信息
  dispatch: Record<string, Function[]> //小型发布订阅通知用户更新了
  constructor(options: Options) {
    this.oldScript = new Set<string>()
    this.newScript = new Set<string>()
    this.dispatch = {}
    this.init() //初始化
    this.timing(options?.timer) //轮询
  }
  static instance: Updater

  static getInstance(options = {}) {
    if (!this.instance) {
      this.instance = new Updater(options)
    }
    return this.instance
  }

  async init() {
    const html: string = await this.getHtml()
    this.oldScript = this.parserScript(html)
  }

  async getHtml() {
    const html = await fetch(import.meta.env.VITE_PUBLIC_PATH ?? '/', {
      method: 'GET',
      cache: 'no-cache', // 禁用浏览器缓存
    }).then((res) => res.text()) //读取index html
    return html
  }

  parserScript(html: string) {
    const reg = new RegExp(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi) //script正则
    return new Set(html.match(reg)) //匹配script标签
  }

  //发布订阅通知
  on(key: 'no-update' | 'update', fn: Function) {
    this.dispatch[key] ??= []
    this.dispatch[key].push(fn)
    return this
  }

  /**
   * 取消订阅
   * @param key
   */
  remove(key: 'no-update' | 'update') {
    delete this.dispatch[key]

    // 如果都没有监听则停止轮询
    if (!Object.keys(this.dispatch).length) {
      this.loopId && clearTimeout(this.loopId)
    }
  }

  compare(oldArr: Set<string>, newArr: Set<string>) {
    const base = oldArr.size
    const arr = Array.from(new Set([...oldArr].concat([...newArr])))

    //如果新旧length 一样无更新
    if (arr.length === base) {
      this.dispatch?.['no-update']?.forEach((fn) => {
        fn()
      })
    } else {
      //否则通知更新
      this.dispatch?.['update']?.forEach((fn) => {
        fn()
      })
    }
  }

  loopId: NodeJS.Timeout | null = null

  timing(time = 10000) {
    this.loopId && clearTimeout(this.loopId)
    //轮询
    this.loopId = setTimeout(async () => {
      const newHtml = await this.getHtml()
      this.newScript = this.parserScript(newHtml)
      this.compare(this.oldScript, this.newScript)

      this.timing(time)
    }, time)
  }
}

export const getUpdateManager = (options) => Updater.getInstance(options)
